/**
 * call 函数的实现步骤：接受参数列表
 * 1. 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况。
 * 2. 判断传入上下文对象是否存在，如果不存在，则设置为 window 。
 * 3. 处理传入的参数，截取第一个参数后的所有参数。
 * 4. 将函数作为上下文对象的一个属性。
 * 5. 使用上下文对象来调用这个方法，并保存返回结果。
 * 6. 删除刚才新增的属性。
 * 7. 返回结果。
 * */
Function.prototype.myCall = function (context) {
    //判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
    if (typeof this !== 'function') {
        throw new TypeError("type error");
    }
    //获取参数
    let args = [...arguments].slice(1)
    // 判断上下文 context 是否传入，如果未传入则设置为 window
    context = context || window;
    //将调用函数设为对象的方法
    context.fn = this
    //调用函数
    let result = context.fn(...args)
    console.log(...args)
    //删除 fn 方法
    delete context.fn
    return result
}
/**
 * apply 函数的实现步骤： 接受数组参数
 * 1. 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况。
 * 2. 判断传入上下文对象是否存在，如果不存在，则设置为 window 。
 * 3. 将函数作为上下文对象的一个属性。
 * 4. 判断参数值是否传入
 * 5. 使用上下文对象来调用这个方法，并保存返回结果。
 * 6. 删除刚才新增的属性
 * 7. 返回结果
 * */
Function.prototype.myApply = function (context) {
    //判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
    if (typeof this !== 'function') {
        throw new TypeError("type error");
    }
    context = context || window;
    context.fn = this
    let result = null
    if (arguments[1]) {
        result = context.fn(...arguments[1])
    } else {
        result = context.fn()
    }
    delete context.fn
    return result
}

/**
 * bind 函数的实现步骤：
 * 1. 判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况。
 * 2. 保存当前函数的引用，获取其余传入参数值。
 * 3. 创建一个函数返回
 * 4. 函数内部使用 apply 来绑定函数调用，需要判断函数作为构造函数的情况，这个时候需要传入当前函数的 this 给 apply 调用，其余情况都传入指定的上下文对象。
 * 绑定函数也可以使用 new 运算符构造，它会表现为目标函数已经被构建完毕了似的。提供的 this 值会被忽略，但前置参数仍会提供给模拟函数。所以说 new 操作符修改this的优先级要比Bind高
 * */
Function.prototype.myBind = function () {
    //判断调用对象是否为函数，即使我们是定义在函数的原型上的，但是可能出现使用 call 等方式调用的情况
    if (typeof this !== 'function') {
        throw new TypeError("type error");
    }
    // 获取参数
    let outContext = arguments[0] // 取上下文
    let outArgs = Array.from(arguments).slice(1) // 取外部入参
    const outThis = this // 存外部this
    let cb = function() {
        const isNew = typeof new.target !== 'undefined' // 判断函数是否被new过
        const inArgs = Array.from(arguments)// 取内部入参
        console.log(this,'thisthis')
        return outThis.apply(isNew ? this : outContext, outArgs.concat(inArgs)) // 改变指向，合并函数入参
    }
    cb.prototype = outThis.prototype // 继承构造函数原型
    return cb // 返回创建函数
}

let obj = {name:1,age:2}
var name = 'Leo', age = 18
function Fn(height, Gender) {
    console.log('name：', this.name, 'age:', this.age,'height:',height, 'Gender:',Gender)
}

var fn1 = Fn.bind(obj, '80cm')

var obj1 = new fn1() // name： undefined age: undefined height: 80cm Gender: undefined